#include "StdAfx.h"
#include "GameEngine.h"
#include "PropertiesPanel.h"
#include "AICoverSurface.h"
#include "AI/AIManager.h"
#include "AI/CoverSurfaceManager.h"
#include "AI/NavDataGeneration/Navigation.h"

#include "PropertiesPanel.h"
#include "Viewport.h"
#include "SegmentedWorld/SegmentedWorldManager.h"

IMPLEMENT_DYNCREATE(CAICoverSurface, CBaseObject);


namespace
{
	int s_propertiesID = 0;
	CPropertiesPanel* s_properties = 0;

	struct PropertiesDeleter
	{
		~PropertiesDeleter()
		{
			delete s_properties;
		}
	} __deleter;
};


CAICoverSurface::CAICoverSurface()
: m_sampler(0)
, m_surfaceID(0)
, m_helperScale(1.0f)
, m_aabb(AABB::RESET)
, m_aabbLocal(AABB::RESET)
{
	m_aabbLocal.Reset();
	UseMaterialLayersMask(false);
	SetColor(RGB(70, 130, 180));
}

CAICoverSurface::~CAICoverSurface()
{
}

bool CAICoverSurface::Init(IEditor* editor, CBaseObject *prev, const CString &file)
{
	if (CBaseObject::Init(editor, prev, file))
	{
		CreatePropertyVars();

		if (prev)
		{
			CAICoverSurface* original = static_cast<CAICoverSurface*>(prev);
			m_propertyValues = original->m_propertyValues;
		}
		else
			SetPropertyVarsFromParams(ICoverSampler::Params()); // defaults

		return true;
	}

	return false;
}

bool CAICoverSurface::CreateGameObject()
{
	if (CBaseObject::CreateGameObject())
	{
		GetIEditor()->GetAI()->GetCoverSurfaceManager()->AddSurfaceObject(this);

		return true;
	}

	return false;
}

void CAICoverSurface::Done()
{
	if (m_sampler)
	{
		m_sampler->Release();
		m_sampler = 0;
	}

	ClearSurface();

	IEditor*	pEditor(GetIEditor());
	CAIManager*	pAIManager(pEditor->GetAI());
	CCoverSurfaceManager* pCoverSurfaceManager=pAIManager->GetCoverSurfaceManager();
	
	pCoverSurfaceManager->RemoveSurfaceObject(this);

	CBaseObject::Done();
}

void CAICoverSurface::DeleteThis()
{
	delete this;
}

void CAICoverSurface::Display(DisplayContext& disp)
{
	if (IsFrozen())
		disp.SetFreezeColor();
	else
		disp.SetColor(GetColor());

	Matrix34 scale(Matrix34::CreateScale(Vec3(gSettings.gizmo.helpersScale * GetHelperScale())));

	disp.RenderObject(STATOBJECT_ANCHOR, GetWorldTM() * scale);

	if (m_sampler)
	{
		switch(m_sampler->Update(0.025f, 2.0f))
		{
		case ICoverSampler::Finished:
			CommitSurface();
			ReleaseSampler();
			break;
		case ICoverSampler::Error:
			ClearSurface();
			ReleaseSampler();
			break;
		default:
			break;
		}
	}

	if (m_surfaceID && IsSelected() && (GetIEditor()->GetSelection()->GetCount() < 15))
	{
		gEnv->pAISystem->GetCoverSystem()->DrawSurface(m_surfaceID);
	}

	DrawDefault(disp);
}

int CAICoverSurface::MouseCreateCallback(CViewport* view, EMouseEvent event, CPoint& point, int flags)
{
	if ((event == eMouseMove) || (event == eMouseLDown))
	{
		if (GetIEditor()->GetAxisConstrains() != AXIS_TERRAIN)
			SetPos(view->MapViewToCP(point));
		else
		{
			bool terrain;
			Vec3 pos = view->ViewToWorld(point, &terrain);
			if (terrain)
				pos.z = GetIEditor()->GetTerrainElevation(pos.x, pos.y) + 0.25f;

			SetPos(view->SnapToGrid(pos));
		}

		if (event == eMouseLDown) {
			SW_TEST_OBJ_PLACETO_MCB(GetPos(), GetLayer(), true);
			SW_ON_OBJ_NEW(this);
			return MOUSECREATE_OK;
		}

		return MOUSECREATE_CONTINUE;
	}

	return CBaseObject::MouseCreateCallback(view, event, point, flags);
}

bool CAICoverSurface::HitTest(HitContext& hitContext)
{
	Vec3 origin = GetWorldPos();
	float radius = GetHelperSize() * GetHelperScale();

	Vec3 w = origin - hitContext.raySrc;
	w = hitContext.rayDir.Cross(w);

	float d = w.GetLengthSquared();
	if (d < (radius * radius) + hitContext.distanceTolerance)
	{
		Vec3 i0;
		if (Intersect::Ray_SphereFirst(Ray(hitContext.raySrc, hitContext.rayDir), Sphere(origin, radius), i0))
		{
			hitContext.dist = hitContext.raySrc.GetDistance(i0);

			return true;
		}

		hitContext.dist = hitContext.raySrc.GetDistance(origin);
		
		return true;
	}

	return false;
}

void CAICoverSurface::GetBoundBox(AABB &aabb)
{
	aabb = m_aabb;

	float extent = GetHelperSize() * GetHelperScale();
	Vec3 world = GetWorldPos();

	aabb.Add(world - Vec3(extent));
	aabb.Add(world + Vec3(extent));
}

void CAICoverSurface::GetLocalBounds(AABB &aabb)
{
	aabb = m_aabbLocal;

	float extent = GetHelperSize() * GetHelperScale();
	aabb.Add(Vec3(-extent));
	aabb.Add(Vec3(extent));
}

void CAICoverSurface::SetHelperScale(float scale)
{
	if(m_helperScale != scale)
	{
		SW_ON_OBJ_MOD(this);
	}
	m_helperScale = scale;
}

float CAICoverSurface::GetHelperScale()
{
	return m_helperScale;
}

float CAICoverSurface::GetHelperSize() const
{
	return 0.5f * gSettings.gizmo.helpersScale;
}

const CoverSurfaceID& CAICoverSurface::GetSurfaceID() const
{
	return m_surfaceID;
}

void CAICoverSurface::SetSurfaceID(const CoverSurfaceID& coverSurfaceID)
{
	m_surfaceID = coverSurfaceID;
}

void CAICoverSurface::Generate()
{
	CreateSampler();
	StartSampling();

	while (m_sampler->Update(2.0f) == ICoverSampler::InProgress);

	if ((m_sampler->GetState() != ICoverSampler::Error) && (m_sampler->GetSampleCount() > 1))
		CommitSurface();

	ReleaseSampler();
}

void CAICoverSurface::ValidateGenerated()
{
	CString error;
	Vec3 pos = GetWorldPos();
	
	CErrorReport* errorReport = GetIEditor()->GetErrorReport();
	ICoverSystem::SurfaceInfo surfaceInfo;

	// Check surface is not empty
	{
		if (!m_surfaceID || 
			!gEnv->pAISystem->GetCoverSystem()->GetSurfaceInfo(m_surfaceID, &surfaceInfo) || (surfaceInfo.sampleCount < 2))
		{
			error.Format("AI Cover Surface '%s' at (%.2f, %.2f, %.2f) is empty!", (const char*)GetName(), pos.x, pos.y, pos.z);
			errorReport->ReportError(CErrorRecord(this, CErrorRecord::ESEVERITY_WARNING, error));

			return;
		}
	}

	CNavigation* navigation = GetIEditor()->GetGameEngine()->GetNavigation();

	int buildingID = -1;
	IVisArea* visArea = 0;

	// Check surface doesn't cross a forbidden area
	bool inside;
	{
		const Vec3& firstSample = surfaceInfo.samples[0].position;
		inside = (navigation->CheckNavigationType(
			firstSample, buildingID, visArea, IAISystem::NAV_WAYPOINT_HUMAN) != IAISystem::NAV_WAYPOINT_HUMAN) &&
			navigation->IsPointInForbiddenRegion(firstSample, 0, true) &&	navigation->IsPointInTriangulationAreas(firstSample);

		for (uint32 i = 1; i < surfaceInfo.sampleCount; ++i)
		{
			const Vec3& sample = surfaceInfo.samples[i].position;
			bool currInside = (navigation->CheckNavigationType(
				sample, buildingID, visArea, IAISystem::NAV_WAYPOINT_HUMAN) != IAISystem::NAV_WAYPOINT_HUMAN) &&
				navigation->IsPointInForbiddenRegion(sample, 0, true) &&	navigation->IsPointInTriangulationAreas(sample);

			if (inside != currInside)
			{
				error.Format("AI Cover Surface '%s' at (%.2f, %.2f, %.2f) crosses a forbidden area near (%.2f, %.2f, %.2f)!",
					(const char*)GetName(), pos.x, pos.y, pos.z, sample.x, sample.y, sample.z);
				errorReport->ReportError(CErrorRecord(this, CErrorRecord::ESEVERITY_WARNING, error));

				return;
			}
		}
	}

	// check if too far from forbidden area
	if (inside)
	{
		for (uint32 i = 0; i < surfaceInfo.sampleCount; ++i)
		{
			const float tolerance = 0.195f;
			const Vec3& sample = surfaceInfo.samples[i].position;

			bool pointInside = (navigation->CheckNavigationType(
				sample, buildingID, visArea, IAISystem::NAV_WAYPOINT_HUMAN) != IAISystem::NAV_WAYPOINT_HUMAN) &&
				navigation->IsPointInForbiddenRegion(sample, 0, true) &&	navigation->IsPointInTriangulationAreas(sample);

			if (pointInside && !navigation->IsPointOnForbiddenEdge(sample, tolerance))
			{
				error.Format("AI Cover Surface '%s' at (%f, %f, %f) is too far away from enclosing forbidden area near (%.2f, %.2f, %.2f)!",
					(const char*)GetName(), pos.x, pos.y, pos.z, sample.x, sample.y, sample.z);
				errorReport->ReportError(CErrorRecord(this, CErrorRecord::ESEVERITY_WARNING, error));

				return;
			}
		}
	}
}

void CAICoverSurface::CreateSampler()
{
  if(!gEnv->pAISystem)
    return;

	if (m_sampler)
		m_sampler->Release();

	m_sampler = gEnv->pAISystem->GetCoverSystem()->CreateCoverSampler();
}

void CAICoverSurface::ReleaseSampler()
{
	if (m_sampler)
	{
		m_sampler->Release();
		m_sampler = 0;
	}
}

void CAICoverSurface::StartSampling()
{
  if(!gEnv->pAISystem)
    return;

	ICoverSampler::Params params(GetParamsFromPropertyVars());

	Matrix34 worldTM = GetWorldTM();
	params.position = worldTM.GetTranslation();
	params.direction = worldTM.GetColumn1();
	
	m_sampler->StartSampling(params);
}

void CAICoverSurface::CommitSurface()
{
	uint32 sampleCount = m_sampler->GetSampleCount();
	if (sampleCount)
	{
		ICoverSystem::SurfaceInfo surfaceInfo;
		surfaceInfo.sampleCount = sampleCount;
		surfaceInfo.samples = m_sampler->GetSamples();
		surfaceInfo.flags = m_sampler->GetSurfaceFlags();

		if (!m_surfaceID)
			m_surfaceID = gEnv->pAISystem->GetCoverSystem()->AddSurface(surfaceInfo);
		else
			gEnv->pAISystem->GetCoverSystem()->UpdateSurface(m_surfaceID, surfaceInfo);
		
		Matrix34 invWorldTM = GetWorldTM().GetInverted();
		m_aabbLocal = AABB(AABB::RESET);

		for (uint32 i = 0; i < surfaceInfo.sampleCount; ++i)
		{
			const ICoverSampler::Sample& sample = surfaceInfo.samples[i];

			Vec3 position = invWorldTM.TransformPoint(sample.position);
			
			m_aabbLocal.Add(position);
			m_aabbLocal.Add(position + Vec3(0.0f, 0.0f, sample.GetHeight()));
		}

		m_aabb = m_sampler->GetAABB();
	}
	else
		ClearSurface();
}

void CAICoverSurface::ClearSurface()
{
	if (m_surfaceID)
	{ 
    if(gEnv->pAISystem)
		  gEnv->pAISystem->GetCoverSystem()->RemoveSurface(m_surfaceID);
		m_surfaceID = CoverSurfaceID(0);
	}

	m_aabb = AABB(AABB::RESET);
	m_aabbLocal = AABB(AABB::RESET);
}

void CAICoverSurface::SetPropertyVarsFromParams(const ICoverSampler::Params& params)
{
	m_propertyValues.limitLeft = params.limitLeft;
	m_propertyValues.limitRight = params.limitRight;

	m_propertyValues.limitDepth = params.limitDepth;
	m_propertyValues.limitHeight = params.limitHeight;

	m_propertyValues.widthInterval = params.widthSamplerInterval;
	m_propertyValues.heightInterval = params.heightSamplerInterval;

	m_propertyValues.maxStartHeight = params.maxStartHeight;
	m_propertyValues.simplifyThreshold = params.simplifyThreshold;

	m_propertyValues.minHeight = params.minHeight;
}

ICoverSampler::Params CAICoverSurface::GetParamsFromPropertyVars()
{
	ICoverSampler::Params params;

	params.limitDepth = m_propertyValues.limitDepth;
	params.limitHeight = m_propertyValues.limitHeight;

	params.limitLeft = m_propertyValues.limitLeft;
	params.limitRight = m_propertyValues.limitRight;

	params.widthSamplerInterval = m_propertyValues.widthInterval;
	params.heightSamplerInterval = m_propertyValues.heightInterval;

	params.maxStartHeight = m_propertyValues.maxStartHeight;
	params.simplifyThreshold = m_propertyValues.simplifyThreshold;

	params.minHeight = m_propertyValues.minHeight;

	return params;
}

void CAICoverSurface::Serialize(CObjectArchive& archive)
{
	CBaseObject::Serialize(archive);

	if (!archive.bUndo)
		SerializeValue(archive, "SurfaceID", m_surfaceID);
	m_propertyValues.Serialize(*this, archive);
}

XmlNodeRef CAICoverSurface::Export(const CString &levelPath, XmlNodeRef &xmlNode)
{
	return 0;
}

void CAICoverSurface::InvalidateTM(int whyFlags)
{
	CBaseObject::InvalidateTM(whyFlags);

	if (!m_sampler)
		CreateSampler();

	StartSampling();
}

void CAICoverSurface::SetSelected(bool bSelected)
{
	CBaseObject::SetSelected(bSelected);

	if (bSelected)
	{
		ClearSurface();
		ReleaseSampler();

		CreateSampler();
		StartSampling();
	}
}

void CAICoverSurface::BeginEditParams(IEditor* editor, int flags)
{
	CBaseObject::BeginEditParams(editor, flags);

	if (!s_properties)
		s_properties = new CPropertiesPanel(AfxGetMainWnd());
	else
		s_properties->DeleteVars();
	s_properties->AddVars(m_propertyVars.get());

	if (!s_propertiesID)
		s_propertiesID = AddUIPage(CString(GetTypeName()) + " Properties", s_properties);

	if(s_properties->GetPropertyCtrl())
		s_properties->GetPropertyCtrl()->SetUpdateObjectCallback( functor(*this,&CBaseObject::OnPropertyChanged) );

}

void CAICoverSurface::EndEditParams(IEditor* editor)
{
	if(s_properties && s_properties->GetPropertyCtrl())
		s_properties->GetPropertyCtrl()->ClearUpdateObjectCallback();

	if (s_propertiesID)
	{
		RemoveUIPage(s_propertiesID);
		s_propertiesID = 0;
		s_properties = 0;
	}

	CBaseObject::EndEditParams(editor);
}

void CAICoverSurface::BeginEditMultiSelParams(bool allSameType)
{
	CBaseObject::BeginEditMultiSelParams(allSameType);

	if (!allSameType)
		return;

	if (!s_properties)
		s_properties = new CPropertiesPanel(AfxGetMainWnd());
	else
		s_properties->DeleteVars();

	s_propertiesID = AddUIPage(CString(GetTypeName()) + " Properties", s_properties);

	if (m_propertyVars.get())
	{
		CSelectionGroup* selectionGroup = GetIEditor()->GetSelection();
		for (int i = 0; i < selectionGroup->GetCount(); ++i)
		{
			CAICoverSurface* surfaceObject = (CAICoverSurface*)selectionGroup->GetObject(i);
			if (CVarBlockPtr surfaceObjectVars = surfaceObject->m_propertyVars.get())
				s_properties->AddVars(surfaceObjectVars);
		}

		if (!s_propertiesID)
			s_propertiesID = AddUIPage(CString(GetTypeName()) + " Properties", s_properties);
	}

	if(s_properties->GetPropertyCtrl())
		s_properties->GetPropertyCtrl()->SetUpdateObjectCallback( functor(*this,&CBaseObject::OnMultiSelPropertyChanged) );

}

void CAICoverSurface::EndEditMultiSelParams()
{
	if(s_properties && s_properties->GetPropertyCtrl())
		s_properties->GetPropertyCtrl()->ClearUpdateObjectCallback();

	if (s_propertiesID)
	{
		RemoveUIPage(s_propertiesID);
		s_propertiesID = 0;
		s_properties = 0;
	}

	CBaseObject::EndEditMultiSelParams();
}

void CAICoverSurface::EnableEditParams(bool bEnable)
{
	CBaseObject::EnableEditParams(bEnable);
	if(s_properties)
		s_properties->SetEnable(bEnable);
}

void CAICoverSurface::EnableEditMultiSelParams(bool bEnable, bool bAllOfSameType)
{
	CBaseObject::EnableEditMultiSelParams(bEnable, bAllOfSameType);
	if(s_properties)
		s_properties->SetEnableMultiSel(bEnable, bAllOfSameType);
}

void CAICoverSurface::OnPropertyVarChange(IVariable* var)
{
	ReleaseSampler();
	CreateSampler();

	StartSampling();
}

void CAICoverSurface::CreatePropertyVars()
{
	m_propertyVars.reset(new CVarBlock());

	m_propertyVars->AddVariable(m_propertyValues.sampler, "Sampler");
	m_propertyVars->AddVariable(m_propertyValues.limitLeft, "Limit Left");
	m_propertyVars->AddVariable(m_propertyValues.limitRight, "Limit Right");
	m_propertyVars->AddVariable(m_propertyValues.limitDepth, "Limit Depth");
	m_propertyVars->AddVariable(m_propertyValues.limitHeight, "Limit Height");

	m_propertyVars->AddVariable(m_propertyValues.minHeight, "Min Height");

	m_propertyVars->AddVariable(m_propertyValues.widthInterval, "Sample Width");
	m_propertyVars->AddVariable(m_propertyValues.heightInterval, "Sample Height");

	m_propertyVars->AddVariable(m_propertyValues.maxStartHeight, "Max Start Height");

	m_propertyVars->AddVariable(m_propertyValues.simplifyThreshold, "Simplification Threshold");

	m_propertyValues.sampler->AddEnumItem(CString("Default"), CString("Default"));
	m_propertyValues.sampler->Set(CString("Default"));

	m_propertyValues.limitLeft->SetLimits(0.0f, 50.0f, 0.05f);
	m_propertyValues.limitRight->SetLimits(0.0f, 50.0f, 0.05f);
	m_propertyValues.limitDepth->SetLimits(0.0f, 10.0f, 0.05f);
	m_propertyValues.limitHeight->SetLimits(0.05f, 50.0f, 0.05f);
	m_propertyValues.minHeight->SetLimits(0.0f, 2.0f, 0.05f);
	m_propertyValues.widthInterval->SetLimits(0.05f, 1.0f, 0.05f);
	m_propertyValues.heightInterval->SetLimits(0.05f, 1.0f, 0.05f);
	m_propertyValues.maxStartHeight->SetLimits(0.0f, 2.0f, 0.05f);
	m_propertyValues.simplifyThreshold->SetLimits(0.0f, 1.0f, 0.005f);

	m_propertyVars->AddOnSetCallback(functor(*this, &CAICoverSurface::OnPropertyVarChange));
}
